
/*
HEADER:         CUG999.03;
TITLE:          SE (nee E) screen editor
DATE:           5/19-87;

DESCRIPTION:    "Text buffer editing routines for se"
VERSION:        1.00;
SYSTEM:         MS-DOS;
FILENAME:       EDIT.C;
SEE-ALSO:       SE.C
AUTHORS:        G. Nigel Gilbert, James W. Haefner, Mel Tearle, G. Osborn;
*/

/*
     e/qed/ged/se screen editor

    (C) G. Nigel Gilbert, MICROLOGY, 1981
           August-December 1981

    FUNCTIONS: movechar, moveline, dojump, jumpline,
               movepage, moveword, insertchar, replchar, deletechar,
               deleteword, linedelete, crdelete, crinsert, adjustc, sync


*/

/* Earlier versions had the capabiltiy of editing text containing embedded
 * tabs.  In that case the character index into text[] is less in magnitude
 * than cursorx.  Due partly to the addition of new functions, charn and
 * cursorx are used interchangably in this version.  This version converts
 * tabs to spaces at the time the file is read.  The capability of editing
 * tabs might be restored as an option in a future version, but probably a
 * better way is to do an automatic entab at disc write time for those who
 * want tabs.  The only advantage of embedded tabs is that they save disc
 * space.  They mess up everything else.  The keyboard tab key is a cursor
 * positioning command in this version.  It does not store anyting.     g.o.
 */

#include <stdio.h>
#include <ctype.h>
#include "ged.h"

/* move cursor by 'move' columns to the right return YES unless going off text
 */
movechar(move)
int  move;
{
    int  cp, len, result, j;

    cp = charn + move;
    result = YES;

    if ( cp < 0 )  {
        if (cline > 1) {
            charn = strlen(getline(cline-1));
            moveline(-1);
        }
    }

/* The "cursor right one char" command adds trailing spaces as needed to
 * provide free cursor movement.  New characters would be lost if preceeded
 * by a '\0'.
 */
    else if ( cp > ( len = strlen(text) ) )  {
        if(!trail && move == 1) {
            sync(cp);
            resetcursor();
            return YES;
        }
        else {
            charn = 0;
            result = moveline(1);
            return result;
        }
    }
    else {
        sync(cp);  /* move +/- one on same line */
        resetcursor();
        return result;
    }
}

/* calculate line number at the top and bottom of screen.  The lines in that
 * span are read from disc if not already in memory.
 *
 * It is convenient to be able to scroll
 * by page for a few pages then page back to the original line with the
 * cursor returning to its original position.  The program works
 * that way for page scrolls.
 */

calp()
{
    if (cursory < topline)
        cursory = topline;
    if ( (cursory - topline) > (cline - 1) )
        cursory = topline + (cline - 1);    /* near bof */
    pfirst = loc(cline + topline - cursory, 0);
    plast = loc(pfirst + SHEIGHT - topline, 0);
    if ( (cursory - topline) > (plast - pfirst) )
        calp(--cursory);  /* neaar eof */
    return;
}

/* Move cursor by '+/- move' lines, + is downward.
 * return YES if Ok, NO if going off text.
 *
 * Set 'cursory' to the preferred cursor y value before the call.  The
 * preferred location is not always be used.  There are conditions near
 * beginning and end of file when it cannot be used.  A scroll
 * placing the y cursor in the preferred positiion can be forced to
 * the extent possible by setting plast=-1 before the call.  The
 * preferred value will otherwise not be used if a one-line
 * scroll suffices, or if the new cursor position is already somewhere
 * on the existing display.  The minimization of scrolling is desirable
 * from a speed standpoint.  It also minimizes eye fatigue.
 *
 * The window movement is normally purely vertical, but there are
 * exceptions.  If 'charn' has recently changed then horizontal
 * display motion may result also.  If 'trail' is true then horizontal
 * motion will often result due to the differing line lengths if
 * the lines are long.  To cause horizontal adjstment on the new
 * line, set charn before the call to moveline().  It is unnecessary
 * to call sync().
 *
 * moveline() assumes that the existing physical display is correct and
 * current.  It can't be called to show the result of an editng operation.
 *
 * It is assumed that the scroll function is faster than a
 * rewrite, so scrolling is used when possible.  Good high-level portability
 * could be achieved by writing a low-level scroll function which might
 * actually do a rewrite when the hardware scroll function is missing.
 * The program is being restructured so that all hardware specific
 * display and terminal functions are in term.c.
 */

moveline(move)
int move;
{
    int line, i, oldoff, sav, ocline;

    puttext();

    ocline = cline;
    cline = loc(cline, move);  /* read from disc if necessary */
    move = cline - ocline;

    sav = curson(NO);
/* gettext adds trailing spaces if needed for free cursor motion */
    gettext(cline, charn);
    sync(charn);

    if (plast < 0)
        pfirst = -1;
    if (pfirst < 0)
        plast = -1;
    oldoff = lastoff;
    lastoff = calcoffset(charn);

    if (blocking && ( move != 1  && move != -1) )
        oldoff = -1;    /* force a complete rewrite if reverse field too complex */

    if (oldoff == lastoff && cline >= pfirst && cline <= plast ) {
/* the new cursor position is already on the screen */

        if (blocking)        /* the vacated line may become unmarked */
            putline(ocline, cursory, NULL);

        cursory = (cline - pfirst) + topline;
        calp();

        if (blocking)         /* the new line may become marked */
            putline(cline, cursory, NULL);

    }
/* scroll down and add one line at top */
/* the old cursor can be anywhere on the screen */
    else if (oldoff == lastoff && cline == pfirst -1) {
        if (blocking)  /* the vacated line may become unmarked */
            putline(ocline, cursory, NULL);

        scrolldown(topline);
        cursory = topline;
        putline(cline,topline,text);
        calp();
    }
/* scroll up and add one line at bottom */
    else if (oldoff == lastoff && cline == plast + 1) {
        if (blocking)  /* the vacated line may become unmarked */
            putline(ocline, cursory, NULL);

        scrollup(topline);
        cursory = SHEIGHT;
        putline(cline, SHEIGHT, text);
        calp();
    }
/* Vertical screen motion of 0 or  more than 1 line, or horiz scrolling. */
/* This case uses the preferred y cursor position if possible */
/* moves of 0 are used to re-establish valid environment */
    else  {
        calp();
        putpage();
    }
    putlineno(cline);  /* update line and column no. */
    resetpcursor();
    curson(sav);       /* ready to edit */
    return  YES;
}

dojump()
{
    int i;

    putmess( "[|+|/|-|]|line|, last |C|hange, |E|nd,  prior |J|ump, |M|ark;  |S|et mark");
    scans(ans,2);
    gotoxy(59,0);
    if ( (i = calcjmp()) > 0)
        jumpline(i - cline);
    return;
}
/* calculate jump to new line.
 * interpret the jump to current line (a NOP) as full screen refresh request.
 * Useful for debugging and from a remote location with transmission errors
 * or outages.
 */
calcjmp()
{
    int i, j, k, to;
    char far *jj;
    i = toupper(ans[0]);
    switch (i) {

    case '+':
        scans(ans,5);
        to = atoi( ans );
        if ( to == 0) {
            cleareop(0);
            putpage();
        }
        jmpto = cline + to;
        break;

    case '-':
        scans(ans,5);
        if ( (to = atoi( ans ) ) )
            jmpto = cline - to;
        break;

    case 'C':
        if (lastc < 1) {
            error("No lines have been changed");
            return cline;
        }
        cursory = topline + SHEIGHT/2;
        jmpto = lastc;  /* line last changed/deleted/inserted */
        break;

    case 'E':
        jmpto = lastl;
        cursory = SHEIGHT - (SHEIGHT - topline)/4;
        break;

    case 'J':
        cursory = topline + SHEIGHT/2;
        return jmpto;
        break;

    case 'M':
        if (linem1 == 0) {
            error("No cursor line was marked with S option");
            return cline;
        }
        else {
            cursory = topline + SHEIGHT/2;
            if (linem1 == cline) {
                if (linem2 > 0)
                    return linem2;
                else
                    return linem1;
            }
            if (linem2 == cline) {
                if (linem3 > 0)
                    return linem3;
                else
                    return linem1;
            }
            return linem1;
        }
        break;

    case 'S':
        linem3 = linem2;
        linem2 = linem1;
        linem1 = cline;   /* mark the current line */
        return cline;
        break;


    case 'W':
        cent();
        return -1;
        break;

    default:
        if ( ans[0] < ' ')
            return -1;

        if ( i >= '0' && i <= '9' ) {
            scans(ans+1,5);
            if ( to = atoi(ans)  ) {
                cursory = topline + SHEIGHT/2;
                jmpto = to;
            }
        }
        else {
            error("Bad parameter");
            return cline;
        }
    }
    return jmpto;
}

/* center window on cursor.  */
cent()
{
    int i,j,k;

    cursory = topline + (SHEIGHT-topline)/2;
    calp();
    offset = cursorx - SWIDTH/2;
    if (offset < 0)
        offset = 0;
/* in centering, don't move right beyond line ends in order to show as
   much text as possible */
    j = 0;
    for(i = pfirst; i <= plast; i++) {
        if (i == cline && altered)
            k = strlen(text);
        else
            k = strlen(getline(i));
        j = ( ( k > j) ? k : j );
    }
    if (j < (offset + SWIDTH) )
        offset =  j - SWIDTH;
    if (offset < 0)
        offset = 0;

    lastoff = -1;
    resetcursor();

    return;
}

/* move current line by move lines down,
 */
jumpline(move)
int move;
{

    puttext();

/* the old cursor position is mostly irrevlelant.  The following
 * avoids excessive horizontal scrolling.
 */
    charn = offset + (offset>0);
    moveline( move );

    return  YES;
}


/* move current line by a page down (dir==0) or up (-1)
 */
movepage(dir)
int dir;
{
    int move, i;

    puttext();

    move = SHEIGHT -topline +1 - PAGEOVERLAP;
    if (dir)
        move = -move;

/* the scroll distance is less than the page height, so a move of
 * 'move' downward from the top line would still be on the screen
 * and no scroll would occur.  force a scroll.
 */
    plast = -1;
    moveline(move);  /* use the existing y cursor */

/* the screen has been updated.  if the disc file is being used then
 * prepare the page containing the next screen for quick access.  In this
 * way the disc access usually occurs after the operator command rather
 * than before it's completion.
 */
    if (move > 0)
        i = plast + SHEIGHT - PAGEOVERLAP;
    else
        i = pfirst - SHEIGHT + PAGEOVERLAP;
    getline(i);

    return;
}


/* move 1 word to the right (move -ve: left)
 */
moveword(move)
int move;
{

    if ( charn + move < 0 )  {
        if (cline > 1) {
            puttext();
            charn = strlen(getline(cline-1));
            moveline(-1);
        }
        else {
            return;
        }
    }
    else if ( charn + move >= strlen(text) )  {
        puttext();
        charn = 0;
        moveline(1);
        if ( inword(text[0]) )
            return;
    }

    while (( move<0 || text[charn]) && inword(text[charn]) && (charn += move))
        ;
    while (( move<0 || text[charn]) && !inword(text[charn]) && (charn += move))
        ;
    if ( move < 0  &&  charn )  {
        while ( inword(text[charn])  &&  --charn )
            ;
        if ( charn || !inword(text[charn]) )  charn++;
    }
    sync( charn );
    resetcursor();
}

/* replace char at cursor position.
   The last character is at text[LLIM-2].  The latest possible
   '\0' is at text[LLIM-1]
 */
replchar(c)
char c;
{
    if ( charn >= LLIM-1 ) {
        error(" Line would be too long ");
        inbufp = 0;
    }
    else {
        if(text[charn] == '\0')
            text[charn+1] = '\0';
        text[charn] = c;
        altered = YES;
        sync( charn + 1 );
        rewrite( charn-1 );  /* updated charn */
    }
    return;
}

/* inserts 'c' at charn, moves cursor up one

   The last character is at text[LLIM-2].  The latest possible
   '\0' is at text[LLIM-1]
 */
insertchar(c)
char c;
{
    int  cp;

    cp = strlen(text);
    if ( cp >= LLIM)
        cerr(20);
    if ( charn > cp)
        cerr(21);

/* prevent line overflow due to trailing spaces */
    if (!trail && text[LLIM-2] == ' ')
        text[LLIM-2] = '\0';

    cp += 1;    /* 1 <=  cp  <= LLIM */
    if ( cp >= LLIM ) {
        error(" Line would be too long ");
/* empy the input buffer to prevent multiple error occurrences if the
   key is held down  */
        inbufp = 0;
    }
    else  {
        for ( ; cp > charn; cp-- )
            text[cp] = text[cp-1];
        text[charn] = c;
        altered = YES;
        sync( charn+1 );
        rewrite( charn-1 );   /* updated charn */
    }
}


/* deletes char before (dir=-1) or at (dir=0) cursor.
 * NOP if charn = 0.
 */
deletechar(dir)
int dir;
{
    char c;
    int  cp;

    cp = charn + dir;
    if ( cp < 0 )
        return;
    else if ( text[cp] == '\0' ) {  /* can't be dir = -1 */
        crdelete();  /* case of cline == lastl handeled by crdelete() */
    }
    else  {
        do  {
            c = text[cp] = text[cp+1];
            cp++;
        }
        while(c);

        altered = YES;
        sync( charn + dir );
        rewrite( charn );  /* updated charn */
    }
}

/* line delete */

linedelete()
{
    int i;

    puttext();
    if (lastl == 1) {
        altered = YES;
        text[0] = '\0';  /* keep a seed */
        sync(0);
        rewrite(0);
    }
    else {
        scrollup(cursory);      /* always clears bottom line */
        if (loc(plast,1) > plast )
            putline( plast+1, SHEIGHT, NULL );   /* add new line at bottom */
/* The screen is updated.  Now do the slow work */
        i = cline;
        if (cline == lastl) {
            cursory -=1;
            cline -= 1;
            putlineno(cline);
        }
        resetpcursor();
        deltp(i, 1);
        calp();
        gettext(cline, charn);
        sync(charn);
    }
/* do a kludged push on the text buffer.  see pop() */
    stkptr = histptr;
    stkcnt = histcnt;
    return;
}


/* Concatenate lines.  Following line is appended to the current line
 * at the cursor position.  The cursor position may be beyond the last
 * visible character if !trail.
 */
crdelete()
{
    int  len, err, *t;
    char textb[LLIM];

    puttext();
    if (text[0] == 0) {
        linedelete();  /* crdelete would work but linedelete looks better for undo */
    }
    else {
        err = 0;
        if (cline < loc(cline,1))
            strcpy( textb, getline(cline+1) );
        else
            return;   /* cline == lastl */

/* have to rewrite first if a right marker will be added */
        if (cursorx - offset == SWIDTH) {
            offset += 1;
            resetcursor();
        }
        len = strlen(text);
        if ( len + strlen(textb) + 1 > LLIM )  {
            textb[(LLIM-1)-len] = '\0';
            error1(" Line too long - cut short ");
            err = 1;
        }

        strcat( text, textb );
        altered = YES;
        puttext();
        putline(cline, cursory, text);
        if (cursory < SHEIGHT) {
            scrollup(cursory+1);             /* clears bottom line */
            if (loc(plast,1) > plast )
                putline( plast+1, SHEIGHT, NULL );   /* add new line at bottom */
        }
        resetcursor();
/* The screen is updated.  Now do the slow work */
        deltp( cline+1, 1 );
        calp();   /* lastl has changed, which can change plast */

/* hold off the status line if line truncated */
        while( (err) && chkbuf() == 0)
            ;
    }
    return;
}


/* deletes up to first occurrence of white space.
 * if no white space then delete to end of line
 * see inword() in ged7.c
 * deletes first space after word as part of the word.
 */
deleteword()
{
    int  pend, cp, in;
    char c;

    for ( in = inword( text[pend = charn] );
      ( c = text[pend] )  &&  ( in ? inword(c): !inword(c) );
      pend++ )
            ;
    if (text[pend] == ' ')
        pend++;
    for ( cp = charn; ( text[cp] = text[pend] ); pend++, cp++ )
        ;
    rewrite( charn );
    altered = YES;
}


/* line break.
 * In mode 0 the portion after the cursor is moved down one line, creating a
 * new line.  The cursor is positioned on the new line unless it was
 * initially in position 0, in which case it remains on the original line
 * in preparation for the entry of a new line.
 * However, if if the cursor is in column 1 of a null line
 * then a new null line is created and the cursor moves
 * down one.  These rules usually accomplish what is wanted with a mimimum
 * of keystrokes.
 *
 * In mode 1 (^N) the cursor always stays on the initial line.  ^N will
 * be used in a future version for sentence push/delete.
 *
 * The injection is done after the screen refresh because it is slow
 * for large documents
 *
 * cursory, cline, and scroll() have to be used with internal consistantly
 * if movline() is not used for linejumps.  calp() must be called if lastl
 * changes due to injection or deletion because cursory and cline have
 * relationships to pfirst and plast.
 */

crinsert(mode)
{
    char textb[LLIM], c;
    int  i, iline, charnb, ocharn, ncline, ocline, ch0;

/* the next steps produce the best reverse-field appearance with undo */
    if (charn != 0)
        altered = YES;

    puttext();
    curson(NO);
    ncline = ocline = cline;
    ocharn = charn;

    charnb = 0;
    if (autoin) {
        for ( ; text[charnb] == ' '; charnb++)
            textb[charnb] = ' ';
        if (charnb > charn)
            charnb = 0;
    }

    strcpy( &textb[charnb], &text[charn] );  /* can be a null line */
    ch0 = text[0];
    text[charn] = '\0';

    if (mode == 0 ) {
        sync(charnb);   /* cursor moves for <ret>, not for ^N
/* horizontal scroll may occur if offset not 0.  may have to rewrite old text first.*/
        if (offset > 0)
            resetcursor();
    }

    if (cursory == SHEIGHT) {
        cursory--;
        scrollup(topline); /* make room for the second half of the split line */
    }
    else
        scrolldown(cursory+1); /* make rooom for the second half */

    putline(cline, cursory, text);
    putline(cline+1, cursory+1, textb);
    if (mode == 0) {
        if (ocharn == 0 && ch0 != 0)
            i = 0;  /* a convenience feature */
        else
            i = 1;
        ncline += i;
        cline = ncline;   /* for consistancy check in resetcursor() */
/* calp() can't be called until after inject() */
        cursory += i;
        putlineno(ncline);
    }
    sync(charn);
    resetpcursor();
    curson(YES);
/* The screen is updated.  Now do the slow work */

/* undo looks better if charn==0 is made a special case */
    if (ocharn == 0) {
        inject (ocline-1, text);
    }
    else {
        cline = ocline;
        altered = YES;
        puttext();              /* left portion of line.  uses 'cline' */
        inject(cline,textb);    /* right portion */
        cline = ncline;
        gettext(cline, charn);         /* get the correct data in text[] */
    }
    calp();           /* lastl has increased */
    return;
}


/* set cursorx to col. cursor nearest to col. 'x' so that
   cursor isn't in the middle of a tab or off the end of the
   current line  +++ obsolete ?
 */
adjustc(x)
int x;
{
    char c;

    for( charn = 0, cursorx = 0;
     cursorx < x  && ( c = text[charn] );
     charn++, cursorx++ )
            if ( c == '\t' )
            cursorx += tabwidth-1-(cursorx % tabwidth);
}


/* put cursorx and charn onto character 'cp' of current line.
 */

sync(cp)
int cp;
{
    int i;

    if (cp < 0)
        cp = 0;
    if (cp > (LLIM-1) )
        cp = LLIM-1;   /* last possible '\0' is at text[LLIM-1] */
    pad(cp);
    for( charn = 0, cursorx = 0; charn < cp; cursorx++, charn++ ) {
        if ( text[charn] == '\t' )
            cursorx = cursorx+tabwidth-1-(cursorx % tabwidth);
    }
    return;
}

/* Add trailing spaces if needed.
 *
 * When a new line is selected it is extended with spaces if necessary
 * in order for the cursor to stay in its old x position.  The cursor
 * can move anywhere.  The spaces are removed before the line is stored.
 *
 * If trail is TRUE then the spaces are not added because in that case
 * trailing spaces in the input file or those added in editing are
 * permanently retained.  In this mode the cursor can't be moved beyond
 * the rightmost character of a line.
 */
pad(cp)
int cp;
{
    int i;

    if( !trail ) {
        i = trim(text);   /* 0  <=  i  <=  LLIM-1  */
        if( cp > i ) {
            for (  ; i < cp; i++)
                text[i] = ' ';
            text[cp] = '\0';
        }
    }
    return;
}


